#pragma once

#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "catalog/column.h"

class Schema;
using SchemaRef = std::shared_ptr<const Schema>;

class Schema {
   public:
    /**
     * Constructs the schema corresponding to the vector of columns, read left-to-right.
     * @param columns columns that describe the schema's individual columns
     */
    Schema(){};
    explicit Schema(const std::vector<Column> &columns);

    static Schema *CopySchema(const Schema *from, const std::vector<uint32_t> &attrs) {
        std::vector<Column> cols;
        cols.reserve(attrs.size());
        for (const auto i : attrs) {
            cols.emplace_back(from->columns_[i]);
        }
        return new Schema{cols};
    }

    /** @return all the columns in the schema */
    const std::vector<Column> &GetColumns() const { return columns_; }

    /**
     * Returns a specific column from the schema.
     * @param col_idx index of requested column
     * @return requested column
     */
    const Column &GetColumn(const uint32_t col_idx) const { return columns_[col_idx]; }

    /**
     * @param col_name name of the wanted column
     * @return the column with the given name
     */

    /**
     * Looks up and returns the index of the first column in the schema with the specified name.
     * If multiple columns have the same name, the first such index is returned.
     * @param col_name name of column to look for
     * @return the index of a column with the given name, throws an exception if it does not exist
     */
    uint32_t GetColIdx(const std::string &col_name) const {
        for (uint32_t i = 0; i < columns_.size(); ++i) {
            if (columns_[i].GetName() == col_name) {
                return i;
            }
        }
        UNREACHABLE("Column does not exist");
    }

    /** @return the indices of non-inlined columns */
    const std::vector<uint32_t> &GetUnlinedColumns() const { return uninlined_columns_; }

    /** @return the number of columns in the schema for the tuple */
    uint32_t GetColumnCount() const { return static_cast<uint32_t>(columns_.size()); }

    /** @return the number of non-inlined columns */
    uint32_t GetUnlinedColumnCount() const { return static_cast<uint32_t>(uninlined_columns_.size()); }

    /** @return the number of bytes used by one tuple */
    inline uint32_t GetLength() const { return length_; }

    /** @return true if all columns are inlined, false otherwise */
    inline bool IsInlined() const { return tuple_is_inlined_; }

    /** @return string representation of this schema */
    std::string ToString() const;

    // 序列化
    void to_json(Json::Value &json_value) const;
    // 反序列化
    static bool from_json(const Json::Value &json_value, Schema &schema);

    void init(uint32_t length, std::vector<Column> columns, bool tuple_is_inlined,
              std::vector<uint32_t> uninlined_columns);

   private:
    /** Fixed-length column size, i.e. the number of bytes used by one tuple. */
    uint32_t length_;

    /** All the columns in the schema, inlined and uninlined. */
    std::vector<Column> columns_;

    /** True if all the columns are inlined, false otherwise. */
    bool tuple_is_inlined_;

    /** Indices of all uninlined columns. */
    std::vector<uint32_t> uninlined_columns_;
};
/*
template <typename T>
struct fmt::formatter<T, std::enable_if_t<std::is_base_of<Schema, T>::value, char>> : fmt::formatter<std::string> {
    template <typename FormatCtx>
    auto format(const bustub::Schema &x, FormatCtx &ctx) const {
        return fmt::formatter<std::string>::format(x.ToString(), ctx);
    }
};

template <typename T>
struct fmt::formatter<std::shared_ptr<T>, std::enable_if_t<std::is_base_of<Schema, T>::value, char>>
    : fmt::formatter<std::string> {
    template <typename FormatCtx>
    auto format(const std::shared_ptr<T> &x, FormatCtx &ctx) const {
        if (x != nullptr) {
            return fmt::formatter<std::string>::format(x->ToString(), ctx);
        }
        return fmt::formatter<std::string>::format("", ctx);
    }
};

template <typename T>
struct fmt::formatter<std::unique_ptr<T>, std::enable_if_t<std::is_base_of<Schema, T>::value, char>>
    : fmt::formatter<std::string> {
    template <typename FormatCtx>
    auto format(const std::unique_ptr<T> &x, FormatCtx &ctx) const {
        if (x != nullptr) {
            return fmt::formatter<std::string>::format(x->ToString(), ctx);
        }
        return fmt::formatter<std::string>::format("", ctx);
    }
};
*/